/*******************************************************************************
  copyright   : Copyright (C) 2012-2019, www.peergine.com, All rights reserved.
  filename    : pgCallFlow.java
  description : 
  modify      : create, chenbichao, 2019/01/02
*******************************************************************************/

package aod.android.com.bernay.fixing.live;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;


public class pgCallFlow
{
	// Error codes.
	public static final int ERROR_OK = 0;
	public static final int ERROR_NoInit = 1;
	public static final int ERROR_RunTime = 2;
	public static final int ERROR_BadStatus = 3;
	public static final int ERROR_SendFailed = 4;
	public static final int ERROR_BadParam = 5;
	public static final int ERROR_Offline = 6;
	public static final int ERROR_Timeout = 7;
	public static final int ERROR_Network = 8;

	// Terminate codes.
	public static final int TERM_Normal = 0;
	public static final int TERM_RunTime = 1;
	public static final int TERM_Busy = 2;
	public static final int TERM_Reject = 3;
	public static final int TERM_Cancel = 4;
	public static final int TERM_NoReply = 5;
	public static final int TERM_NoAnswer = 6;
	public static final int TERM_Offline = 7;
	public static final int TERM_NetFailed = 8;

	// Constructor.
	public pgCallFlow() {
	}

	// initialize the paramters.
	public int Initialize(String sLocalID, String sInfo, int iTimeoutNoAnswer, int iTimeoutReply) {
		try {
			int iErr = ERROR_OK;

			synchronized(m_iAtomic) {
				if (m_sLocalID.equals("")) {
					if (Looper.myLooper() != null) {
						m_handlerEvent = new _PGHandler();
						m_bHasHandler = true;
					}
		
					m_sLocalID = sLocalID;
					m_sInfo = sInfo;
					if (iTimeoutNoAnswer > 0) {
						m_iTimeoutNoAnswer = iTimeoutNoAnswer * 1000;
					}
					if (iTimeoutReply > 0) {
						m_iTimeoutReply = iTimeoutReply * 1000;
					}
				}
			}

			return iErr;
		}
		catch (Exception ex) {
			_OutString("pgCallFlow.Initialize: ex=" + ex.toString());
			return ERROR_RunTime;
		}
	}

	public void Cleanup() {
		try {
			synchronized(m_iAtomic) {
				_TimerCancelNoAnswer();
				_TimerCancelReply();

				_StatusReset();
				m_handlerEvent = null;
				m_bHasHandler = false;
				m_sLocalID = "";
				m_sInfo = "";
			}
		}
		catch (Exception ex) {
			_OutString("pgCallFlow.Cleanup: ex=" + ex.toString());
		}
	}

	// Calling handlers.
	public int Request(String sCallID, String sInfo) {
		try {
			int iErr = ERROR_OK;

			synchronized(m_iAtomic) {
				if (m_sLocalID.equals("")) {
					iErr = ERROR_NoInit;
				}
				else if (m_iCallingSide != -1
					|| (m_iCallStatus != CALLING_Idle && m_iCallStatus != CALLED_Idle))
				{
					iErr = ERROR_BadStatus;
				}
				else {
					if (_TimerStartNoAnswer()) {
						int iSessionID = _SessionGen(m_iSessionID);
						String sData = _MessageBuild(iSessionID, m_sLocalID, sCallID, ACTION_Request, sInfo);
						if (_MessageSendCallback(sCallID, sData)) {
							m_iCallingSide = 1;
							m_iCallStatus = CALLING_Requesting;
							m_iSessionID = iSessionID;
							m_sCallID = sCallID;
						}
						else {
							_TimerCancelNoAnswer();
							iErr = ERROR_SendFailed;
						}
					}
					else {
						iErr = ERROR_RunTime;
					}
				}
			}

			return iErr;
		}
		catch (Exception ex) {
			_OutString("pgCallFlow.Request: ex=" + ex.toString());
			return ERROR_RunTime;
		}
	}

	public int Cancel(String sInfo) {
		try {
			int iErr = ERROR_OK;
			String sCallID = "";

			synchronized(m_iAtomic) {
				if (m_sLocalID.equals("")) {
					iErr = ERROR_NoInit;
				}
				else if (m_iCallingSide != 1
					|| (m_iCallStatus != CALLING_Requesting && m_iCallStatus != CALLING_Ringing))
				{
					iErr = ERROR_BadStatus;
				}
				else {
					_TimerCancelNoAnswer();
					String sData = _MessageBuild(m_iSessionID, m_sLocalID, m_sCallID, ACTION_Cancel, sInfo);
					_MessageSendCallback(m_sCallID, sData);
					sCallID = _StatusReset();
				}
			}

			if (!sCallID.equals("")) {
				_EventCallback(EVENT_Terminate, sCallID, "", TERM_Cancel);
			}

			return iErr;
		}
		catch (Exception ex) {
			_OutString("pgCallFlow.Cancel: ex=" + ex.toString());
			return ERROR_RunTime;
		}
	}

	public int Accept(String sInfo) {
		try {
			int iErr = ERROR_OK;
			String sCallID = "";

			synchronized(m_iAtomic) {
				if (m_sLocalID.equals("")) {
					iErr = ERROR_NoInit;
				}
				else if (m_iCallingSide != 0 || m_iCallStatus != CALLED_Requesting) {
					iErr = ERROR_BadStatus;
				}
				else {
					_TimerCancelNoAnswer();
					if (_TimerStartReply()) {
						String sData = _MessageBuild(m_iSessionID, m_sCallID, m_sLocalID, ACTION_Accept, sInfo);
						if (_MessageSendCallback(m_sCallID, sData)) {
							m_iCallStatus = CALLED_Establishing;
						}
						else {
							_TimerCancelReply();
							sCallID = _StatusReset();
							iErr = ERROR_SendFailed;
						}
					}
					else {
						sCallID = _StatusReset();
						iErr = ERROR_RunTime;
					}
				}
			}

			if (!sCallID.equals("")) {
				int iTermCode = (iErr == ERROR_SendFailed) ? TERM_NetFailed : TERM_RunTime;
				_EventCallback(EVENT_Terminate, sCallID, "", iTermCode);
			}

			return iErr;
		}
		catch (Exception ex) {
			_OutString("pgCallFlow.Accept: ex=" + ex.toString());
			return ERROR_RunTime;
		}
	}

	public int Reject(String sInfo) {
		try {
			int iErr = ERROR_OK;
			String sCallID = "";

			synchronized(m_iAtomic) {
				if (m_sLocalID.equals("")) {
					iErr = ERROR_NoInit;
				}
				else if (m_iCallingSide != 0 || m_iCallStatus != CALLED_Requesting) {
					iErr = ERROR_BadStatus;
				}
				else {
					_TimerCancelNoAnswer();
					String sData = _MessageBuild(m_iSessionID, m_sCallID, m_sLocalID, ACTION_Reject, sInfo);
					_MessageSendCallback(m_sCallID, sData);
					sCallID = _StatusReset();
				}
			}

			if (!sCallID.equals("")) {
				_EventCallback(EVENT_Terminate, sCallID, "", TERM_Reject);
			}

			return iErr;
		}
		catch (Exception ex) {
			_OutString("pgCallFlow.Reject: ex=" + ex.toString());
			return ERROR_RunTime;
		}
	}

	public int Terminate(String sInfo) {
		try {
			int iErr = ERROR_OK;
			String sCallID = "";

			synchronized(m_iAtomic) {
				if (m_sLocalID.equals("")) {
					iErr = ERROR_NoInit;
				}
				else if (m_iCallingSide == 0) {
					if (m_iCallStatus != CALLED_Establishing && m_iCallStatus != CALLED_Established) {
						iErr = ERROR_BadStatus;
					}
					else {
						_TimerCancelReply();
						if (_TimerStartReply()) {
							String sData = _MessageBuild(m_iSessionID, m_sCallID, m_sLocalID, ACTION_TermRequest, sInfo);
							if (_MessageSendCallback(m_sCallID, sData)) {
								m_iCallStatus = CALLED_Terminating;
							}
							else {
								_TimerCancelReply();
								sCallID = _StatusReset();
								iErr = ERROR_SendFailed;
							}
						}
						else {
							sCallID = _StatusReset();
							iErr = ERROR_RunTime;
						}
					}
				}
				else if (m_iCallingSide == 1) {
					if (m_iCallStatus != CALLED_Established) {
						iErr = ERROR_BadStatus;
					}
					else {
						if (_TimerStartReply()) {
							String sData = _MessageBuild(m_iSessionID, m_sLocalID, m_sCallID, ACTION_TermRequest, sInfo);
							if (_MessageSendCallback(m_sCallID, sData)) {
								m_iCallStatus = CALLING_Terminating;
							}
							else {
								_TimerCancelReply();
								sCallID = _StatusReset();
								iErr = ERROR_SendFailed;
							}
						}
						else {
							sCallID = _StatusReset();
							iErr = ERROR_RunTime;
						}
					}
				}
				else {
					iErr = ERROR_BadStatus;
				}
			}

			if (!sCallID.equals("")) {
				int iTermCode = (iErr == ERROR_SendFailed) ? TERM_NetFailed : TERM_RunTime;
				_EventCallback(EVENT_Terminate, sCallID, "", iTermCode);
			}

			return iErr;
		}
		catch (Exception ex) {
			_OutString("pgCallFlow.Terminate: ex=" + ex.toString());
			return ERROR_RunTime;
		}
	}


	// Process the receiving message.
	public void MessageProc(String sCallID, String sData) {
		try {
			MsgData msgData = _MessageParse(sData);
			if (msgData == null || !msgData.bResult) {
				return;
			}

			int iEventCode = -1;
			int iEventTermCode = -1;
			String sEventCallID = "";
			String sEventInfo = "";

			synchronized(m_iAtomic) {
				switch (msgData.iAction) {
				case ACTION_Request:
					if (m_iCallingSide < 0) {
						if (_TimerStartNoAnswer()) {
							String sDataReply = _MessageBuild(msgData.iSessionID,
								msgData.sCallerID, msgData.sCalledID, ACTION_Ringing, m_sInfo);
							if (_MessageSendCallback(msgData.sCallerID, sDataReply)) {
								m_iCallingSide = 0;
								m_iCallStatus = CALLED_Requesting;
								m_iSessionID = msgData.iSessionID;
								m_sCallID = msgData.sCallerID;
	
								iEventCode = EVENT_Request;
								sEventCallID = msgData.sCallerID;
								sEventInfo = msgData.sInfo;
							}
							else {
								_TimerCancelNoAnswer();
								_OutString("pgCallFlow.MessageProc: send 'ringing' message failed!");
							}
						}
						else {
							String sDataReply = _MessageBuild(msgData.iSessionID,
								msgData.sCallerID, msgData.sCalledID, ACTION_Reject, m_sInfo);
							_MessageSendCallback(msgData.sCallerID, sDataReply);
							_OutString("pgCallFlow.MessageProc: start timer failed!");
						}
					}
					else {
						if (msgData.iSessionID != m_iSessionID) {
							String sDataReply = _MessageBuild(msgData.iSessionID,
								msgData.sCallerID, msgData.sCalledID, ACTION_Busy, msgData.sInfo);
							_MessageSendCallback(msgData.sCallerID, sDataReply);
						}
						else {
							_OutString("pgCallFlow.MessageProc: duplicate 'request' message!");
						}
					}
					break;

				case ACTION_Busy:
					if (m_iCallingSide == 1
						&& msgData.iSessionID == m_iSessionID
						&& m_iCallStatus == CALLING_Requesting)
					{
						_TimerCancelNoAnswer();
						_StatusReset();

						iEventCode = EVENT_Terminate;
						iEventTermCode = TERM_Busy;
						sEventCallID = msgData.sCalledID;
						sEventInfo = msgData.sInfo;
					}
					break;

				case ACTION_Ringing:
					if (m_iCallingSide == 1
						&& msgData.iSessionID == m_iSessionID
						&& m_iCallStatus == CALLING_Requesting)
					{
						m_iCallStatus = CALLING_Ringing;
						iEventCode = EVENT_Ringing;
						sEventCallID = msgData.sCalledID;
						sEventInfo = msgData.sInfo;
					}
					break;

				case ACTION_Accept:
					if (m_iCallingSide == 1
						&& msgData.iSessionID == m_iSessionID
						&& m_iCallStatus == CALLING_Ringing)
					{
						_TimerCancelNoAnswer();

						String sDataReply = _MessageBuild(msgData.iSessionID,
							msgData.sCallerID, msgData.sCalledID, ACTION_Establish, m_sInfo);
						if (_MessageSendCallback(m_sCallID, sDataReply)) {
							m_iCallStatus = CALLING_Established;
							iEventCode = EVENT_Establish;
							sEventCallID = msgData.sCalledID;
							sEventInfo = msgData.sInfo;
						}
						else {
							_StatusReset();
							iEventCode = EVENT_Terminate;
							iEventTermCode = TERM_NetFailed;
							sEventCallID = msgData.sCalledID;
							sEventInfo = msgData.sInfo;
						}
					}
					break;

				case ACTION_Reject:
					if (m_iCallingSide == 1
						&& msgData.iSessionID == m_iSessionID
						&& m_iCallStatus == CALLING_Ringing)
					{
						_TimerCancelNoAnswer();
						_StatusReset();

						iEventCode = EVENT_Terminate;
						iEventTermCode = TERM_Reject;
						sEventCallID = msgData.sCalledID;
						sEventInfo = msgData.sInfo;
					}
					break;

				case ACTION_Cancel:
					if (m_iCallingSide == 0
						&& msgData.iSessionID == m_iSessionID
						&& (m_iCallStatus == CALLED_Requesting || m_iCallStatus == CALLED_Establishing))
					{
						_TimerCancelNoAnswer();
						_TimerCancelReply();
						_StatusReset();

						iEventCode = EVENT_Terminate;
						iEventTermCode = TERM_Cancel;
						sEventCallID = msgData.sCallerID;
						sEventInfo = msgData.sInfo;
					}
					break;

				case ACTION_NoAnswer:
					if (m_iCallingSide == 1
						&& msgData.iSessionID == m_iSessionID
						&& m_iCallStatus == CALLING_Ringing)
					{
						_TimerCancelNoAnswer();
						_StatusReset();

						iEventCode = EVENT_Terminate;
						iEventTermCode = TERM_NoAnswer;
						sEventCallID = msgData.sCalledID;
						sEventInfo = msgData.sInfo;
					}
					break;

				case ACTION_Establish:
					if (m_iCallingSide == 0
						&& msgData.iSessionID == m_iSessionID
						&& m_iCallStatus == CALLED_Establishing)
					{
						_TimerCancelReply();

						m_iCallStatus = CALLED_Established;
						iEventCode = EVENT_Establish;
						sEventCallID = msgData.sCallerID;
						sEventInfo = msgData.sInfo;
					}
					break;

				case ACTION_TermRequest:
					String sDataReply = _MessageBuild(msgData.iSessionID,
						msgData.sCallerID, msgData.sCalledID, ACTION_TermConfirm, "");
					_MessageSendCallback(sCallID, sDataReply);

					if (m_iCallingSide == 0
						&& msgData.iSessionID == m_iSessionID
						&& m_iCallStatus == CALLED_Established)
					{
						_StatusReset();
						iEventCode = EVENT_Terminate;
						iEventTermCode = TERM_Normal;
						sEventCallID = msgData.sCallerID;
						sEventInfo = msgData.sInfo;
					}
					else if (m_iCallingSide == 1
						&& msgData.iSessionID == m_iSessionID
						&& (m_iCallStatus == CALLING_Ringing || m_iCallStatus == CALLING_Established))
					{
						_StatusReset();
						iEventCode = EVENT_Terminate;
						iEventTermCode = TERM_Normal;
						sEventCallID = msgData.sCalledID;
						sEventInfo = msgData.sInfo;
					}
					break;

				case ACTION_TermConfirm:
					if (m_iCallingSide == 0
						&& msgData.iSessionID == m_iSessionID
						&& m_iCallStatus == CALLED_Terminating)
					{
						_TimerCancelReply();
						_StatusReset();

						iEventCode = EVENT_Terminate;
						iEventTermCode = TERM_Normal;
						sEventCallID = msgData.sCallerID;
						sEventInfo = msgData.sInfo;
					}
					else if (m_iCallingSide == 1
						&& msgData.iSessionID == m_iSessionID
						&& m_iCallStatus == CALLING_Terminating)
					{
						_TimerCancelReply();
						_StatusReset();

						iEventCode = EVENT_Terminate;
						iEventTermCode = TERM_Normal;
						sEventCallID = msgData.sCalledID;
						sEventInfo = msgData.sInfo;
					}
					break;

				default:
					_OutString("pgCallFlow.MessageProc: receive invalid message!");
					break;
				}
			}

			_EventCallback(iEventCode, sEventCallID, sEventInfo, iEventTermCode);
		}
		catch (Exception ex) {
			_OutString("pgCallFlow.MessageProc: ex=" + ex.toString());
		}
	}

	public void MessageError(int iError) {
		try {
			int iEventCode = -1;
			int iEventTermCode = -1;
			String sEventCallID = "";
			String sEventInfo = "";

			synchronized(m_iAtomic) {
				switch (iError) {
				case ERROR_BadParam:
				case ERROR_Timeout:
				case ERROR_Network:
					if ((m_iCallingSide == 0 && m_iCallStatus != CALLED_Idle)
						|| (m_iCallingSide == 1 && m_iCallStatus != CALLING_Idle))
					{
						_TimerCancelNoAnswer();
						_TimerCancelReply();

						sEventCallID = _StatusReset();
						iEventCode = EVENT_Terminate;
						iEventTermCode = TERM_NetFailed;
					}
					break;

				case ERROR_Offline:
					if ((m_iCallingSide == 0 && m_iCallStatus != CALLED_Idle)
						|| (m_iCallingSide == 1 && m_iCallStatus != CALLING_Idle))
					{
						_TimerCancelNoAnswer();
						_TimerCancelReply();

						sEventCallID = _StatusReset();
						iEventCode = EVENT_Terminate;
						iEventTermCode = TERM_Offline;
					}
					break;
				}
			}

			_EventCallback(iEventCode, sEventCallID, sEventInfo, iEventTermCode);
		}
		catch (Exception ex) {
			_OutString("pgCallFlow.MessageError: ex=" + ex.toString());
		}
	}

	public String GetErrorText(int iError) {
		switch (iError) {
		case ERROR_OK:
			return "Success";

		case ERROR_NoInit:
			return "Not initialize";

		case ERROR_RunTime:
			return "Run time error";

		case ERROR_BadStatus:
			return "Invalid status";

		case ERROR_SendFailed:
			return "Send message failed";

		case ERROR_BadParam:
			return "Invalid parameters";

		case ERROR_Offline:
			return "Other side is offline";

		case ERROR_Timeout:
			return "Request timeout";

		case ERROR_Network:
			return "Network error";

		default:
			return "Unknown error";
		}
	}
	
	public String GetTerminateText(int iTermCode) {
		switch (iTermCode) {
		case TERM_Normal:
			return "Handup success";

		case TERM_RunTime:
			return "Run time error";

		case TERM_Busy:
			return "Other side is busy";

		case TERM_Reject:
			return "Calling is reject";

		case TERM_Cancel:
			return "Calling is cancel";

		case TERM_NoReply:
			return "Calling is no reply";

		case TERM_NoAnswer:
			return "Calling is no answer";

		case TERM_Offline:
			return "Other side is offline";

		case TERM_NetFailed:
			return "Network failed";

		default:
			return "Unknown code";
		}
	}


	// Message tranfer overide methods.
	protected boolean onMessageSend(String sCallID, String sData) {
		return false;
	}

	// Calling event callback methods.
	protected void onRequest(String sCallID, String sInfo) {
	}

	protected void onRinging(String sCallID, String sInfo) {
	}

	protected void onEstablish(String sCallID, String sInfo) {
	}

	protected void onTerminate(String sCallID, int iCode, String sInfo) {
	}


	//--------------------------------------------------------------------------
	// Private member and method.

	// Call action codes.
	private static final int ACTION_Null = 0;
	private static final int ACTION_Request = 1;
	private static final int ACTION_Busy = 2;
	private static final int ACTION_Ringing = 3;
	private static final int ACTION_Accept = 4;
	private static final int ACTION_Reject = 5;
	private static final int ACTION_Cancel = 6;
	private static final int ACTION_NoAnswer = 7;
	private static final int ACTION_Establish = 8;
	private static final int ACTION_TermRequest = 9;
	private static final int ACTION_TermConfirm = 10;

	// Calling status codes.
	private static final int CALLING_Idle = 0;
	private static final int CALLING_Requesting = 1;
	private static final int CALLING_Ringing = 2;
	private static final int CALLING_Established = 3;
	private static final int CALLING_Terminating = 4;

	// Called status codes.
	private static final int CALLED_Idle = 0;
	private static final int CALLED_Requesting = 1;
	private static final int CALLED_Establishing = 2;
	private static final int CALLED_Established = 3;
	private static final int CALLED_Terminating = 4;

	// Event codes.
	private static final int EVENT_Request = 0;
	private static final int EVENT_Ringing = 1;
	private static final int EVENT_Establish = 2;
	private static final int EVENT_Terminate = 3;

	// Event handler
	private boolean m_bHasHandler = false;
	private _PGHandler m_handlerEvent = null;

	// Config parameters.
	private String m_sLocalID = "";
	private String m_sInfo = "";
	private int m_iTimeoutNoAnswer = TIMEOUT_NO_ANSWER;
	private int m_iTimeoutReply = TIMEOUT_REPLY;

	// Call status members.
	private AtomicInteger m_iAtomic = new AtomicInteger();
	private int m_iCallingSide = -1;
	private int m_iCallStatus = 0;
	private int m_iSessionID = 0;
	private String m_sCallID = "";
	
	// Call status reset
	private String _StatusReset() {
		String sCallID = m_sCallID;
		m_iCallingSide = -1;
		m_iCallStatus = 0;
		m_iSessionID = 0;
		m_sCallID = "";
		return sCallID;
	}

	// Message build and parse.
	class MsgData {
		public boolean bResult = false;
		public int iSessionID = 0;
		public int iAction = 0;
		public String sCallerID = "";
		public String sCalledID = "";
		public String sInfo = "";
		public MsgData() {
		}
	}

	private String _MessageBuild(int iSessionID, String sCallerID, String sCalledID, int iAction, String sInfo) {
		String sData = "session=" + iSessionID + "|callerid=" + sCallerID
			+ "|calledid=" + sCalledID + "|action=" + iAction + "|info=" + sInfo;
		return sData;
	}

	private MsgData _MessageParse(String sData) {
		try {
			MsgData msgData = new MsgData();

			String[] sList = sData.split("[|]+");
			for (int i = 0; i < sList.length; i++) {
				String[] sParam = sList[i].split("[=]+", 2);
				if (sParam.length >= 2) {
					if (sParam[0].equals("session")) {
						msgData.iSessionID = _ParseInt(sParam[1], 0);
					}
					else if (sParam[0].equals("action")) {
						msgData.iAction = _ParseInt(sParam[1], 0);
					}
					else if (sParam[0].equals("callerid")) {
						msgData.sCallerID = sParam[1];
					}
					else if (sParam[0].equals("calledid")) {
						msgData.sCalledID = sParam[1];
					}
					else if (sParam[0].equals("info")) {
						msgData.sInfo = sParam[1];
					}
				}
			}

			if (msgData.iSessionID != 0
				&& msgData.iAction != 0
				&& !msgData.sCallerID.equals("")
				&& !msgData.sCalledID.equals(""))
			{
				msgData.bResult = true;
			}
			else {
				_OutString("pgCallFlow._MessageParse: invalid message format. sData=" + sData);
			}

			return msgData;
		}
		catch (Exception ex) {
			_OutString("pgCallFlow._MessageParse: ex=" + ex.toString());
			return null;
		}
	}

	// Session id genernal.
	private java.util.Random m_Random = new java.util.Random();

	private int _SessionGen(int iSessionIdOld) {
		int iTemp = 0;
		while (true) {
			iTemp = m_Random.nextInt();
			if (iTemp < 0) {
				iTemp = -iTemp;
			}
			if (iTemp != 0 && iTemp != iSessionIdOld) {
				break;
			}
		}
		return iTemp;
	}

	// Message send callback.
	private boolean _MessageSendCallback(String sCallID, String sData) {
		try {
			return onMessageSend(sCallID, sData);
		}
		catch (Exception ex) {
			_OutString("pgCallFlow._MessageSendCallback: 'onMessageSend' ex=" + ex.toString());
			return false;
		}
	}

	// Event process callback, include exception catch.
	class _PGHandler extends Handler {
		public void handleMessage(Message msg) {

			int iEventCode = -1;
			int iTermCode = -1;
			String sCallID = "";
			String sInfo = "";

			String sParamList = (String)(msg.obj);
			String[] sList = sParamList.split("[|]+");
			for (int i = 0; i < sList.length; i++) {
				String[] sParam = sList[i].split("[=]+", 2);
				if (sParam.length >= 2) {
					if (sParam[0].equals("event")) {
						iEventCode = _ParseInt(sParam[1], 0);
					}
					else if (sParam[0].equals("callid")) {
						sCallID = sParam[1];
					}
					else if (sParam[0].equals("info")) {
						sInfo = sParam[1];
					}
					else if (sParam[0].equals("termcode")) {
						iTermCode = _ParseInt(sParam[1], 0);
					}
				}
			}

			pgCallFlow.this._EventCallbackPriv(iEventCode, sCallID, sInfo, iTermCode);
		}
	}

	private void _EventCallback(int iEventCode, String sCallID, String sInfo, int iTermCode) {
		boolean bHandlerOK = false;
		if (m_bHasHandler && m_handlerEvent != null) {
			try {
				String sParamList = "event=" + iEventCode + "|callid=" + sCallID
					+ "|info=" + sInfo + "|termcode=" + iTermCode;
				Message msg = m_handlerEvent.obtainMessage(0, 0, 0, new String(sParamList));
				m_handlerEvent.sendMessage(msg);
				bHandlerOK = true;
			}
			catch (Exception ex) {
				_OutString("pgCallFlow._EventCallback: handler send. ex=" + ex.toString());
			}
		}
		
		if (!bHandlerOK) {
			_EventCallbackPriv(iEventCode, sCallID, sInfo, iTermCode);
		}
	}

	private void _EventCallbackPriv(int iEventCode, String sCallID, String sInfo, int iTermCode) {
		switch (iEventCode) {
		case EVENT_Request:
			try {
				onRequest(sCallID, sInfo);
			}
			catch (Exception ex) {
				_OutString("pgCallFlow._EventCallbackPriv: 'onRequest' ex=" + ex.toString());
			}
			break;

		case EVENT_Ringing:
			try {
				onRinging(sCallID, sInfo);
			}
			catch (Exception ex) {
				_OutString("pgCallFlow._EventCallbackPriv: 'onRinging' ex=" + ex.toString());
			}
			break;

		case EVENT_Establish:
			try {
				onEstablish(sCallID, sInfo);
			}
			catch (Exception ex) {
				_OutString("pgCallFlow._EventCallbackPriv: 'onEstablish' ex=" + ex.toString());
			}
			break;

		case EVENT_Terminate:
			try {
				onTerminate(sCallID, iTermCode, sInfo);
			}
			catch (Exception ex) {
				_OutString("pgCallFlow._EventCallbackPriv: 'onTerminate' ex=" + ex.toString());
			}
			break;
		}
	}


	//--------------------------------------------------------------------------
	// Timers
	private static final int TIMEOUT_NO_ANSWER = 60 * 1000;
	private static final int TIMEOUT_REPLY = 5 * 1000;

	private Timer m_timerNoAnswer = null;
	private Timer m_timerReply = null;
	
	private TimerTask m_taskNoAnswer = null;
	private TimerTask m_taskReply = null;
	
	private boolean m_bTimerNoAnswerRun = false;
	private boolean m_bTimerReplyRun = false;

	private void _TimerProcNoAnswer() {
		try {
			int iEventCode = -1;
			int iEventTermCode = -1;
			String sEventCallID = "";
			String sEventInfo = "";

			synchronized(m_iAtomic) {
				if (m_iCallingSide == 0) {
					if (m_iCallStatus == CALLED_Requesting) {
						String sDataReply = _MessageBuild(m_iSessionID,
							m_sCallID, m_sLocalID, ACTION_NoAnswer, m_sInfo);
						_MessageSendCallback(m_sCallID, sDataReply);
	
						sEventCallID = _StatusReset();
						iEventCode = EVENT_Terminate;
						iEventTermCode = TERM_NoAnswer;
						sEventInfo = "";
					}
				}
				else if (m_iCallingSide == 1) {
					if (m_iCallStatus == CALLING_Requesting
						|| m_iCallStatus == CALLING_Ringing)
					{
						String sDataReply = _MessageBuild(m_iSessionID,
							m_sLocalID, m_sCallID, ACTION_Cancel, m_sInfo);
						_MessageSendCallback(m_sCallID, sDataReply);
	
						sEventCallID = _StatusReset();
						iEventCode = EVENT_Terminate;
						iEventTermCode = TERM_NoAnswer;
						sEventInfo = "";
					}
				}

				m_bTimerNoAnswerRun = false;
				m_timerNoAnswer = null;
				m_taskNoAnswer = null;
			}

			if (!sEventCallID.equals("")) {
				_EventCallback(iEventCode, sEventCallID, sEventInfo, iEventTermCode);
			}
		}
		catch (Exception ex) {
			_OutString("pgCallFlow._TimerProcNoAnswer: ex=" + ex.toString());
			m_bTimerNoAnswerRun = false;
			m_timerNoAnswer = null;
			m_taskNoAnswer = null;
		}
	}
	
	private void _TimerProcReply() {
		try {
			int iEventCode = -1;
			int iEventTermCode = -1;
			String sEventCallID = "";
			String sEventInfo = "";

			synchronized(m_iAtomic) {
				if (m_iCallingSide == 0) {
					if (m_iCallStatus == CALLED_Establishing
						|| m_iCallStatus == CALLED_Terminating)
					{
						String sDataReply = _MessageBuild(m_iSessionID,
							m_sCallID, m_sLocalID, ACTION_TermRequest, m_sInfo);
						_MessageSendCallback(m_sCallID, sDataReply);
	
						sEventCallID = _StatusReset();
						iEventCode = EVENT_Terminate;
						iEventTermCode = TERM_NoReply;
						sEventInfo = "";
					}
				}
				else if (m_iCallingSide == 1) {
					if (m_iCallStatus == CALLING_Terminating) {
						sEventCallID = _StatusReset();
						iEventCode = EVENT_Terminate;
						iEventTermCode = TERM_NoReply;
						sEventInfo = "";
					}
				}

				m_bTimerReplyRun = false;
				m_timerReply = null;
				m_taskReply = null;
			}

			if (!sEventCallID.equals("")) {
				_EventCallback(iEventCode, sEventCallID, sEventInfo, iEventTermCode);
			}
		}
		catch (Exception ex) {
			_OutString("pgCallFlow._TimerProcReply: ex=" + ex.toString());
			m_bTimerReplyRun = false;
			m_timerReply = null;
			m_taskReply = null;
		}
	}

	private boolean _TimerStartNoAnswer() {
		try {
			boolean bResult = false;
			if (!m_bTimerNoAnswerRun) {
				m_taskNoAnswer = new TimerTask() {
					@Override
					public void run() {
						pgCallFlow.this._TimerProcNoAnswer();
					}
				};
				m_timerNoAnswer = new Timer();
				m_timerNoAnswer.schedule(m_taskNoAnswer, m_iTimeoutNoAnswer);
				m_bTimerNoAnswerRun = true;
				bResult = true;
			}
			return bResult;
		}
		catch (Exception ex) {
			_OutString("pgCallFlow._TimerStartNoAnswer: ex=" + ex.toString());
			return false;
		}
	}

	private boolean _TimerStartReply() {
		try {
			boolean bResult = false;
			if (!m_bTimerReplyRun) {
				m_taskReply = new TimerTask() {
					@Override
					public void run() {
						pgCallFlow.this._TimerProcReply();
					}
				};
				m_timerReply = new Timer();
				m_timerReply.schedule(m_taskReply, m_iTimeoutReply);
				m_bTimerReplyRun = true;
				bResult = true;
			}
			return bResult;
		}
		catch (Exception ex) {
			_OutString("pgCallFlow._TimerStartReply: ex=" + ex.toString());
			return false;
		}
	}

	private void _TimerCancelNoAnswer() {
		try {
			if (m_bTimerNoAnswerRun) {
				if (m_timerNoAnswer != null) {
					m_timerNoAnswer.cancel();
					m_timerNoAnswer = null;
				}
				if (m_taskNoAnswer != null) {
					m_taskNoAnswer.cancel();
					m_taskNoAnswer = null;
				}
				m_bTimerNoAnswerRun = false;
			}
		}
		catch (Exception ex) {
			_OutString("pgCallFlow._TimerCancelNoAnswer: ex=" + ex.toString());
			m_bTimerNoAnswerRun = false;
		}
	}

	private void _TimerCancelReply() {
		try {
			if (m_bTimerReplyRun) {
				if (m_timerReply != null) {
					m_timerReply.cancel();
					m_timerReply = null;
				}
				if (m_taskReply != null) {
					m_taskReply.cancel();
					m_taskReply = null;
				}
				m_bTimerReplyRun = false;
			}
		}
		catch (Exception ex) {
			_OutString("pgCallFlow._TimerCancelReply: ex=" + ex.toString());
			m_bTimerReplyRun = false;
		}
	}


	//--------------------------------------------------------------------------
	// Static utilize methods
	private static int _ParseInt(String sVal, int idefVal) {
		try {
			if (sVal.equals("")) {
				return idefVal;
			}
			return Integer.parseInt(sVal);
		}
		catch (Exception ex) {
			return idefVal;
		}
	}

	private static void _OutString(String sOut) {
		System.out.println(sOut);
	}
}
